//
//  NSTimerOnFrameViewController.m
//  CoreAnimation
//
//  Created by 张国文 on 16/10/31.
//  Copyright © 2016年 hangzhouyijiyin. All rights reserved.
//

#import "NSTimerOnFrameViewController.h"

@interface NSTimerOnFrameViewController ()

@property (nonatomic, strong) UIView *backgroundView;

@property (nonatomic, strong) UIImageView *ballImageV;

@property (nonatomic, strong) NSTimer *timer;

@property (nonatomic, assign) NSTimeInterval duration;

@property (nonatomic, assign) NSTimeInterval timeOffset;

@property (nonatomic, strong) id fromValue;

@property (nonatomic, strong) id toValue;

@property (nonatomic, assign) NSInteger retainProgressCount; // 记录计时器的次数

@end

@implementation NSTimerOnFrameViewController

// 这一节和运用关键帧的区别就是，这节不运用关键帧而是使用NSTimer(定时帧)代替关键帧进行动画，

/**
 *  定时帧：
 动画看起来是用来显示一段连续的运动过程，但实际上当在固定位置上展示像素的时候并不能做到这一点。一般来说这种显示都无法做到连续的移动，能做的仅仅是足够快地展示一系列静态图片，只是看起来像是做了运动。
 
 我们之前提到过iOS按照每秒60次刷新屏幕，然后CAAnimation计算出需要展示的新的帧，然后在每次屏幕更新的时候同步绘制上去，CAAnimation最机智的地方在于每次刷新需要展示的时候去计算插值和缓冲。
 我们运用NSTimer来模拟CAAnimation计算关键帧的过程。原理：我们之前运用NSTimer来对钟表的指针做定时动画，一秒钟更新一次，但是如果我们把频率调整成一秒钟更新60次的话，原理是完全相同的
 */

/**
*   NSTimer并不是最佳方案，为了理解这点，我们需要确切地知道NSTimer是如何工作的。iOS上的每个线程都管理了一个NSRunloop，字面上看就是通过一个循环来完成一些任务列表。但是对主线程，这些任务包含如下几项：

1、处理触摸事件
2、发送和接受网络数据包
3、执行使用gcd的代码
4、处理计时器行为
5、屏幕重绘

当你设置一个NSTimer，他会被插入到当前任务列表中，然后直到指定时间过去之后才会被执行。但是何时启动定时器并没有一个时间上限，而且它只会在列表中上一个任务完成之后开始执行。这通常会导致有几毫秒的延迟，但是如果上一个任务过了很久才完成就会导致延迟很长一段时间。

屏幕重绘的频率是一秒钟六十次，但是和定时器行为一样，如果列表中上一个执行了很长时间，它也会延迟。这些延迟都是一个随机值，于是就不能保证定时器精准地一秒钟执行六十次。有时候发生在屏幕重绘之后，这就会使得更新屏幕会有个延迟，看起来就是动画卡壳了。有时候定时器会在屏幕更新的时候执行两次，于是动画看起来就跳动了。
*/

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.timer invalidate];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor whiteColor];
    self.backgroundView = [[UIView alloc] initWithFrame:self.view.frame];
    self.backgroundView.backgroundColor = [UIColor colorWithRed:150 / 255.0 green:150 / 255.0 blue:150 / 255.0 alpha:1];
    [self.view addSubview:_backgroundView];
    
    self.ballImageV = [[UIImageView alloc] initWithFrame:CGRectMake(150, 50, 64, 64)]; // 此时center 182,82
    self.ballImageV.image = [UIImage imageWithName:@"ball"];
    [self.backgroundView addSubview:_ballImageV];
    
   
    
    UIButton *startButton = [UIButton buttonWithType:UIButtonTypeCustom];
    startButton.frame = CGRectMake(0, 450, 375, 30);
    startButton.backgroundColor = [UIColor whiteColor];
    [startButton setTitle:@"开始小球掉落动画" forState:UIControlStateNormal];
    [startButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
    [startButton addTarget:self action:@selector(start:) forControlEvents:UIControlEventTouchUpInside];
    startButton.tag = 100;
    [self.backgroundView addSubview:startButton];
}

- (void)start:(UIButton *)button
{
    self.fromValue = [NSValue valueWithCGPoint:CGPointMake(182, 82)];
    self.toValue = [NSValue valueWithCGPoint:CGPointMake(182, 400)];
    self.duration = 2.0;
    self.timeOffset = 0.0;
    [self.timer invalidate]; // 这个方法会在timer存在的时候移除timer，不存在的时候什么也不做
    self.retainProgressCount = 0;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 / 60.0 target:self selector:@selector(goTime:) userInfo:nil repeats:YES];

    // 这个是测试NSTimer的工作方式，你会发现会在输出完1~1000个数字之后，计时器才会运行
    for (int i = 0; i < 1000; i++) {
        NSLog(@"%d", i);
    }

}

- (void)goTime:(NSTimer *)timer
{
    NSLog(@"开始");
    self.timeOffset = MIN(self.timeOffset + 1 / 60.0, self.duration); // 最大到self.duration
    float time = self.timeOffset / self.duration;
    time = bounceEaseOut1(time);
    id position = [self interpolateFromValue:self.fromValue toValue:self.toValue time:time];
    self.ballImageV.layer.position = [position CGPointValue];
    if (self.timeOffset == self.duration) {
        [self.timer invalidate];
        self.timer = nil;
        if (self.timer) {
            NSLog(@"执行");
        } else {
            NSLog(@"不执行");
        }
         NSLog(@"次数=%ld", self.retainProgressCount); // 输出结果为120次
    }
    self.retainProgressCount++;
}



// 获取对应时间的值
float interpolate1(float from, float to, float time)
{
    return (to - from) * time + from;
}

- (id)interpolateFromValue:(id)fromValue toValue:(id)toValue time:(float)time
{
    // 判断类型
    if ([fromValue isKindOfClass:[NSValue class]]) {
        // 获取具体类型
        const char *type = [fromValue objCType];
        // NSLog(@"类型：%s", type);
        // @encode将类型编码成为objCType
        if (strcmp(type, @encode(CGPoint)) == 0) {
            CGPoint from = [fromValue CGPointValue];
            CGPoint to = [toValue CGPointValue];
            
            CGPoint result = CGPointMake(interpolate1(from.x, to.x, time), interpolate1(from.y, to.y, time));
            if (result.y > 400) {
                
            } else {
                return [NSValue valueWithCGPoint:result];
            }
            
        }
    }
    // 如果不是NSValue类型的，返回一个安全值
    return (time < 0.5)? fromValue :toValue;
    
}

// 这可以起到作用，但效果并不是很好，到目前为止我们所完成的只是一个非常复杂的方式来使用线性缓冲复制CABasicAnimation的行为。这种方式的好处在于我们可以更加精确地控制缓冲，这也意味着我们可以应用一个完全定制的缓冲函数。那么该如何做呢？缓冲背后的数学并不很简单，但是幸运的是我们不需要一一实现它。罗伯特·彭纳有一个网页关于缓冲函数（http://www.robertpenner.com/easing），包含了大多数普遍的缓冲函数的多种编程语言的实现的链接，包括C。

float bounceEaseOut1(float t)
{
    if (t < 4/11.0) {
        return (121 * t * t)/16.0;
    } else if (t < 8/11.0) {
        return (363/40.0 * t * t) - (99/10.0 * t) + 17/5.0;
    } else if (t < 9/10.0) {
        return (4356/361.0 * t * t) - (35442/1805.0 * t) + 16061/1805.0;
    }
    return (54/5.0 * t * t) - (513/25.0 * t) + 268/25.0;
}


@end


